<?php

require_once("crypto/RSA.php");
require_once("crypto/AES.php");
require_once("crypto/BigInteger.php");

class JsonRpcClient
{

	/* Init the client with Json-RPC-Server 's URL
	 * @param $server_url The URL of Server
	 */
	function __construct($server_url)
	{
		$this->_server_url = $server_url;
		$this->_id = rand();

		//Close encrypt by default
		$this->_encrypt = false;
	}


	/* Make json request according to method's name and param
	 * @param $method The name of RPC method
	 * @param $params The param for the rpc call
	 * @return The JSON-String for request or null if params is not an array
	 */
	private function makeJsonRequest($method,$params)
	{
		//Check for $method
		if(!is_string($method))
		{
			throw new Exception("\$method must be an string.");
		}

		//Check for $params
		if(!is_array($params))
		{
			throw new Exception("\$param must be an Array.");
		}

		//Make the request
		$req = Array();
		$req["id"] = $this->_id;
		$req["method"] = $method;
		$req["params"] = $params;
		return $req;
	}

	/* Post data to server and get response.
	 * @param $data Data you want to post
	 * @return The response by server
	 */
	private function postData($data)
	{
		$opt = Array();
		$opt["http"] = Array();
		$opt["http"]["method"] = "POST";
		$opt["http"]["content"] = $data;
		//		$opt["http"]["header"] = "Connection:Close\r\n";

		//print_r($opt);

		$s_context = stream_context_create($opt);

		$result = @file_get_contents($this->_server_url,false,$s_context);
		if(!$result)
		{
			throw new Exception("json-rpc-e-server currently unavailable , ".$this->_server_url);
		}
		else
		{
			return $result;
		}
	}


	/* Change the server's response into result array in PHP
	 * @param full response by server
	 * @return the "result" in form of array
	 * @exception if "error" is not null or other abnormal condition , throw Exception
	 *
	 */
	private function parseResult($resp)
	{
		$result = json_decode($resp,true);

		//Check for id
		if($result["id"]!=$this->_id)
		{
			throw new Exception("Rpc call fail , response id is not match.");
		}

		//Check for error
		if(@!is_null($result["error"]))
		{
			//error occour
			throw new Exception("Rpc call fail , ".$result["error"]);
		}

		//Check for result
		if(@is_null($result["result"]))
		{
			throw new Exception("Rpc call fail , result is null.");
		}

		return $result["result"];
	}

	/* If in safe-mode , need get RSA-pubkey and encrypt in AES
	 *
	 */
	private function autoEncrypt($req,&$aes)
	{
		if(!$this->_encrypt)
		{
			//not need to encrypt
			return json_encode($req);
		}
		else
		{
			//Get the RSA key
			$clientTmp = new JsonRpcClient($this->_server_url);
			$clientTmp->setEncrypt(false);
			$param = Array();
			$result = $clientTmp->call($this->RSA_PUBKEY_METHOD(),$param);
			$split_arr = split("\t",$result);
			$rsa_e = $split_arr[0];
			$rsa_n = $split_arr[1];
			$rsa_key = Array();
			$rsa_key['e'] =  new Math_BigInteger($rsa_e);
			$rsa_key['n'] =  new Math_BigInteger($rsa_n);
			$this->_rsa = new Crypt_RSA();
			$this->_rsa->loadKey($rsa_key,CRYPT_RSA_PUBLIC_FORMAT_RAW);

			//encrypt params in AES->Base64
			$aes_key = $this->randomKey(16);
			echo $aes_key."<br />";
			$aes = new Crypt_AES();
			$aes->setKey($aes_key);
			$params = $req["params"];
			$req["params"] = base64_encode($aes->encrypt(json_encode($params)));
			//aes_key->RSA and set in request
			$req[JsonRpcClient::AES_KEY()] = base64_encode($this->_rsa->encrypt($aes_key));
			return json_encode($req);
		}
	}

	/* Generate an random key by length
	 *
	 * @param $len
	 */
	private function randomKey($length)
	{
		echo $pLen."<br />";
		for($i=0;$i<$length;$i++)
		{
			$key.=chr(mt_rand(33, 126));
		}
		return $key;
	}

	/* If in safe mode decrypt the result
	 * @param "result" in AES->Base64
	 * @param aes(contains aes key)
	 */
	private function autoDecrypt($result,$aes)
	{
		if(!$this->_encrypt)
		{
			//Not need to decrypt
			return $result;
		}
		else
		{
			//decrypt
			$cliper = base64_decode($result);
			return $aes->decrypt($cliper);
		}
	}

	public static function AES_KEY()
	{
		return '__aes_key_by_rsa__';
	}

	public static function RSA_PUBKEY_METHOD()
	{
		return 'system.getRsaPubKey';
	}

	/* Make the RPC Call
	 * @param $method The method name that registered on remote rpc-Server
	 * @param $params The params with this rpc-Call,must be an array
	 */
	function call($method,$params)
	{
		$aes;//aes-key

		//Make the json request
		$req_json = $this->autoEncrypt($this->makeJsonRequest($method,$params),$aes);

		//Open the stream , POST data
		$resp = $this->postData($req_json);

		//Parse the response
		$result = $this->autoDecrypt($this->parseResult($resp),$aes);
		//success make the result
		return $result;
	}

	/* Set url of rpc-server
	 * @param $url RPC-Server 's URL
	 */
	function setServerUrl($url)
	{
		$this->_server_url = $url;
	}

	/* Get url of rpc-server
	 * @return url of rpc-server
	 */
	function getServerUrl()
	{
		return $this->_server_url;
	}

	function setEncrypt($encrypt)
	{
		$this->_encrypt = $encrypt;
	}


	/* All the properties */

	private $_server_url;	//The url for Json-RPC-Server(HTTP based version)
	private $_id;	//The id of call,increment by 1 after each call

	private $_encrypt;	//true for safe mode(RSA+AES)

	private $_rsa;	//RSA Object

}

?>